/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2010.

	This file is part of the Maxsi Library.

	Maxsi Library is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Library is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
	License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Library. If not, see <http://www.gnu.org/licenses/>.

	Maxsi Library
	A powerful Cross-Platform C++ General Purpose Library that allows you to
	efficiently create high-performance and reliable applications.

	MaxsiBufferAssembler.cpp
	A simple system for combining multiple binary streams into one.

****************************************************************************/

#include "MaxsiLibrary.h"

BeginMaxsiNamespace

typedef List BufferAssembler;

#define BUFFER_SIZE (16*1024)

BufferAssembler CreateBufferAssembler()
{
	List ResultList		=	CreateList(1);

	if ( ResultList == NULL ) { return NULL; }

	size_t	Usage	=	0;

	if ( SetListEntry(ResultList, 0, (BYTE*)&Usage, sizeof(Usage)) == false ) { DeleteList(ResultList); return NULL; }

	BufferAssembler Result		=	(BufferAssembler)ResultList;

	return Result;
}

MaxsiError WriteBufferData(BufferAssembler Assembler, BYTE* Data, size_t DataSize)
{
	List BufferList		=	(List)Assembler;

	// Read the current usage of the last buffer!
	size_t*	UsageP		=	(size_t*)GetListEntry(BufferList, 0);

	if ( GetListSize(BufferList) == 1 )
	{
		if ( DataSize > BUFFER_SIZE )
		{
			if ( AddListEntry(&BufferList, Data, DataSize) == false ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
			*UsageP = DataSize;
		}
		else
		{
			BYTE Temp[BUFFER_SIZE];
			memcpy(Temp, Data, DataSize);
			if ( AddListEntry(&BufferList, Temp, BUFFER_SIZE) == false ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
			*UsageP = DataSize;
		}
	}
	else
	{
		if ( *UsageP >= BUFFER_SIZE  && DataSize >= BUFFER_SIZE) // The last buffer is full
		{
			if ( AddListEntry(&BufferList, Data, DataSize) == false ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
			*UsageP = DataSize;
		}
		else if ( DataSize > (BUFFER_SIZE - *UsageP) ) // The last buffer does not have room for the entire input
		{
			BYTE*	LastBuffer	=	GetListEntry(BufferList, GetListSize(BufferList)-1);
	
			size_t LastBufferWritten = BUFFER_SIZE - *UsageP;

			memcpy(LastBuffer + *UsageP, Data, LastBufferWritten);

			size_t DataLeft = DataSize - LastBufferWritten;

			if ( DataLeft > BUFFER_SIZE ) // The remaining input is still larger than BUFFER_SIZE
			{
				if ( AddListEntry(&BufferList, Data + LastBufferWritten, DataLeft) == false ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
				*UsageP = DataLeft;
			}
			else // The remaining input fits into a new fragment.
			{
				if ( AddListEntry(&BufferList, LastBuffer, BUFFER_SIZE) == false ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }
				BYTE*	NewBuffer	=	GetListEntry(BufferList, GetListSize(BufferList)-1);
				memcpy(NewBuffer, Data + LastBufferWritten, DataSize - LastBufferWritten);
				*UsageP = DataSize - LastBufferWritten;					
			}
		}
		else // The last buffer has room for its data.
		{
			BYTE*	LastBuffer	=	GetListEntry(BufferList, GetListSize(BufferList)-1);
			memcpy(LastBuffer + *UsageP, Data, DataSize);
			*UsageP += DataSize; 
		}
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError WriteBufferDataA(BufferAssembler Assembler, char* Data)
{
	return WriteBufferData(Assembler, (BYTE*)Data, strlen(Data));
}

MaxsiError WriteBufferDataU(BufferAssembler Assembler, UTF8_t* Data)
{
	return WriteBufferData(Assembler, (BYTE*)Data, strlen((char*)Data));
}

MaxsiError WriteBufferDataW(BufferAssembler Assembler, wchar_t* Data)
{
	return WriteBufferData(Assembler, (BYTE*)Data, wcslen(Data));
}

BYTE* AssembleBuffer(BufferAssembler Assembler)
{
	List BufferList		=	(List)Assembler;

	size_t	NumBuffers	=	GetListSize(BufferList);
	size_t*	UsageP		=	(size_t*)GetListEntry(BufferList, 0);
	size_t	ResultSize	=	*UsageP;

	// Calculate the size of the combined buffer.
	for ( size_t I = 1; I < NumBuffers - 1; I++ ) { ResultSize += GetListEntrySize(BufferList, I); }

	// Allocate a destination buffer!
	BYTE*	Result		=	new BYTE[ResultSize + 1];

	if ( Result == NULL ) { return NULL; }

	ResultSize	=	0;

	// Copy all the fragments to the destination buffer!
	for ( size_t I = 1; I < NumBuffers - 1; I++ )
	{
		BYTE*	Src		=	GetListEntry(BufferList, I);
		size_t	Size	=	GetListEntrySize(BufferList, I);

		memcpy(Result + ResultSize, Src, Size);

		ResultSize += Size;
	}

	PrintOutput("The first many %zu buffers had alltogether %zu bytes!\n", NumBuffers, ResultSize);

	// Copy the last fragment.
	memcpy(Result + ResultSize, GetListEntry(BufferList, NumBuffers-1), *UsageP);
	
	ResultSize += *UsageP;

	Result[ResultSize] = 0;

	return Result;
}

void DeleteBufferAssembler(BufferAssembler Assembler)
{
	DeleteList((List)Assembler);
}

EndMaxsiNamespace

